$1000$ lidí si hodí $100\times$ mincí, a sečte počet orlů.
import numpy as np
toss = np.random.choice([0, 1], size=[1000, 100]) # vrhy
toss_sums = np.sum(toss, axis=1) # secti orly
import seaborn as sns
sns.histplot(toss_sums, discrete=True); # histogram
Histogramy umožňují analyzovat rozdělení dat graficky.
Na naší nepoctivé minci padá orel s pravděpodobností $52\%$.
toss2 = np.random.choice([0, 1], size=[1000, 100], p=[.48, .52])
toss2_sums = np.sum(toss2, axis=1)
sns.histplot(toss_sums, discrete=True, label='0.5'); # poctiva
sns.histplot(toss2_sums, discrete=True, label='0.52'); # nepoctiva
plt.legend();
Liší se oranžový histogram od modrého?
Můžeme si to otestovat statisticky.
$\chi^2$ test porovnává dva histogramy, $h$ a $g$.
# histogramy
h = np.histogram(toss_sums, bins=range(100+1))[0]
g = np.histogram(toss2_sums, bins=range(100+1))[0]
# testovaci statistika
test_stat = 0
for i in range(100):
if h[i] > 0:
test_stat += (h[i] - g[i])**2 / h[i]
test_stat # testovaci statistika
197.7241088701197
Převedeme si testovací statistiku na p-hodnotu (pomocí $\chi^2$ rozdělení).
scipy.stats.chi2.sf(test_stat, 100-1) # vyznamne kdyz <0.05
1.4826819136900469e-08
Histogramy se liší. Něco je špatně s naší mincí!
x = np.array(Image.open('hora.png')) # nahraj obrazek
plt.imshow(x,cmap = "gray");
sns.histplot(x.flatten(), discrete=True); # histogram obalky
y = lsbr(x, 1.) # ukryj steganografii
sns.histplot(y.flatten(), discrete=True); # stego histogram
Podívejme se zblízka. Co se nám to děje s histogram?
fig, ax = plt.subplots(1, 3, sharey=True)
for i, alpha in enumerate([.0, .5, 1.]):
y = lsbr(x, alpha).flatten()
sns.histplot(y, binrange=(125, 175), discrete=True, ax=ax[i]);
ax[i].set_title(f'{alpha:.1f}');
LSB nahrazování způsobuje, že se vyrovnávají sousední sloupce (sudý a lichý).
# histogram
h, edges = np.histogram(x.flatten(), bins=range(256+1))
# prumer sudy-lichy
hbar = np.repeat((h[:-1:2] + h[1::2]) / 2, 2)
fig, ax = plt.subplots(1, 2, sharey=True)
ax[0].bar(range(256), h);
ax[1].bar(range(256), hbar);
Jak můžeme využít $\chi^2$ test pro zjištění přítomnosti steganografie?
Když se histogram podobá tomu se zprůměrovanými páry, steganografie je přítomna.
# zamezeni deleni nulou
h = h[hbar > 0]
hbar = hbar[hbar > 0]
# chi2 test
test_stat = np.sum((h - hbar)**2 / hbar)
pvalue = scipy.stats.chi2.sf(test_stat, 2**8-1)
pvalue # stego kdyz >0.05
0.0
P-hodnota je menší než $5\%$, histogramy se liší. Obrázek je obálka.
Provedeme $\chi^2$-test na stego obrázku.
def chi2_attack(x):
# histograms
h = np.histogram(x.flatten(), bins=range(256+1))[0]
hbar = np.repeat((h[:-1:2] + h[1::2])/2, 2)
h, hbar = h[hbar > 0], hbar[hbar > 0]
# chi2 test
S = np.sum((h[:-1:2] - hbar[::2])**2 / hbar[::2])
return scipy.stats.chi2.sf(S, h.size-1)
y = lsbr(x, 1.) # vytvor stego
pvalue = chi2_attack(y) # chi2 utok
pvalue # stego kdyz >0.05
1.0
P-hodnota je větší než $5\%$, histogramy se liší. Obrázek je stego.
# nacti podezrele obrazky
dum = np.array(Image.open('dum.png'))
socha = np.array(Image.open('socha.png'))
stena = np.array(Image.open('stena.png'))
Zkus $\chi^2$ test na každém obrázku.
chi2_attack(dum)
0.0
chi2_attack(socha)
0.0
chi2_attack(stena)
1.0
key = 42 # odpoved na otazku zivota
message = extract_lsbr(stena, key=key)
print(message[:300], '...')
Božena Němcová: Babička Úvod Dávno, dávno již tomu, co jsem posledně se dívala do té milé mírné tváře, co jsem zulíbala to bledé líce, plné vrásků, nahlížela do modrého oka, v němž se jevilo tolik dobroty a lásky, dávno tomu, co mne posledně žehnaly staré její ruce! - Není více dobré stařenky! Dáv ...
print(extract_lsbr(dum)) # ukryty Easter egg
Cau Vereno! Je mi lito, ze jsi ztratila klic od kola. Co se dnes potkat u stojanu na kola, abychom odrizli tvuj zamek. Martin.